package site.yunnong.atvris.service.impl;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import site.yunnong.atvris.common.dto.InteractionParam;
import site.yunnong.atvris.common.enumeration.MediaTypeEnum;
import site.yunnong.atvris.common.utils.RedisKeyUtils;
import site.yunnong.atvris.dao.domain.UserStar;
import site.yunnong.atvris.dao.domain.VideoProfile;
import site.yunnong.atvris.service.InteractionService;
import site.yunnong.atvris.service.UserStarService;
import site.yunnong.atvris.service.VideoProfileService;
import site.yunnong.atvris.service.helper.OperatorHelper;

import javax.annotation.Resource;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * @author zjh
 * @date 2021/9/9 10:43
 */
@Slf4j
@Service
public class InteractionServiceImpl implements InteractionService {

    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private VideoProfileService videoProfileService;
    @Resource
    private UserStarService userStarService;

    @Override
    public void click(InteractionParam param) {
        // 未用到uuid
        // 媒体点击量
        String key = RedisKeyUtils.getMediaKey(param.getMediaType(), param.getMediaId());
        // 用ip标识该用户是否在一定时间内点击过
        String mediaClickTimeLimitKey = RedisKeyUtils.getMediaClickTimeLimitKey(param.getMediaType(), param.getMediaId(), param.getIp());
        // 是否在限制时间内重复点击
        long count = redisTemplate.opsForValue().increment(mediaClickTimeLimitKey, 1);
        if (count == 1) {
            // 过期时间为3分钟，3分钟内的重复点击不计算点击量
            redisTemplate.expire(mediaClickTimeLimitKey, 3, TimeUnit.MINUTES);
            // 媒体点击量++
            redisTemplate.opsForHash().increment(key, RedisKeyUtils.clickCountHashKey, 1);
            if (MediaTypeEnum.VIDEO.getValue().equals(param.getMediaType())) {
                // todo 计算视频综合得分
                // 计算视频分数
                String mediaScoreKey = RedisKeyUtils.getMediaScoreKey(MediaTypeEnum.VIDEO.getValue());
                redisTemplate.opsForSet().add(mediaScoreKey, param.getMediaId());
            }
        }
    }

    @Override
    public void like(InteractionParam param) {
        Long uuid = OperatorHelper.getLoginUuid();
        // redis 更新
        try {
            handleLikeChange(uuid, param.getMediaType(), param.getMediaId(), param.getMediaUuid());
        } catch (Exception e) {
            log.error("点赞redis操作失败，原因: {}", e.getMessage());
        }
        // todo 触发点赞事件，发送点赞消息
        // 计算视频分数
        if (MediaTypeEnum.VIDEO.getValue().equals(param.getMediaType())) {
            String mediaScoreKey = RedisKeyUtils.getMediaScoreKey(MediaTypeEnum.VIDEO.getValue());
            redisTemplate.opsForSet().add(mediaScoreKey, param.getMediaId());
        }
    }

    private void handleLikeChange(Long uuid, Integer mediaType, Long mediaId, Long mediaUuid) {
        redisTemplate.execute(new SessionCallback() {
            @Override
            public Object execute(RedisOperations operations) throws DataAccessException {
                // 点赞比较特殊，每个用户对于单个媒体只能点赞一次
                // 1、媒体点赞统计量++/--
                String mediaKey = RedisKeyUtils.getMediaKey(mediaType, mediaId);
                // 3、用户与媒体点赞关联关系
                String mediaLikeSetKey = RedisKeyUtils.getMediaLikeSetKey(mediaType, mediaId);
                // 2、用户获赞统计量++/--
                String userKey = RedisKeyUtils.getUserKey(mediaUuid);

                Boolean isMember = operations.opsForSet().isMember(mediaLikeSetKey, uuid);

                operations.multi();

                if (isMember) {
                    // 媒体点赞统计量--
                    operations.opsForHash().increment(mediaKey, RedisKeyUtils.likedCountHashKey, -1);
                    // 媒体点赞集合，删掉该记录
                    operations.opsForSet().remove(mediaLikeSetKey, uuid);
                    // 用户获赞量 -1
                    operations.opsForHash().increment(userKey, RedisKeyUtils.likedCountHashKey, -1);
                } else {
                    // 媒体点赞统计量++
                    operations.opsForHash().increment(mediaKey, RedisKeyUtils.likedCountHashKey, 1);
                    operations.opsForSet().add(mediaLikeSetKey, uuid);
                    operations.opsForHash().increment(userKey, RedisKeyUtils.likedCountHashKey, 1);
                }

                return operations.exec();
            }
        });
    }

    @Override
    public void view(InteractionParam param) {
        // 未用到uuid
        // 用ip标识该用户是否在一定时间内播放过
        String mediaViewTimeLimitKey = RedisKeyUtils.getMediaViewTimeLimitKey(param.getMediaType(), param.getMediaId(), param.getIp());
        // 是否在限制时间内重复播放
        long count = redisTemplate.opsForValue().increment(mediaViewTimeLimitKey, 1);
        if (count == 1) {
            // 过期时间为3分钟，3分钟内的重复播放不计算点击量
            redisTemplate.expire(mediaViewTimeLimitKey, 3, TimeUnit.MINUTES);
            // 1、媒体统计量++
            String mediaKey = RedisKeyUtils.getMediaKey(param.getMediaType(), param.getMediaId());
            redisTemplate.opsForHash().increment(mediaKey, RedisKeyUtils.viewCountHashKey, 1);
            // 2、媒体所属用户统计量++
            String userKey = RedisKeyUtils.getUserKey(param.getMediaUuid());
            redisTemplate.opsForHash().increment(userKey, RedisKeyUtils.viewCountHashKey, 1);
            // 计算视频评分
            if (MediaTypeEnum.VIDEO.getValue().equals(param.getMediaType())) {
                String mediaScoreKey = RedisKeyUtils.getMediaScoreKey(MediaTypeEnum.VIDEO.getValue());
                redisTemplate.opsForSet().add(mediaScoreKey, param.getMediaId());
            }
        }
    }

    @Override
    public boolean findMediaLikeStatus(Long uuid, Integer mediaType, Long mediaId) {
        String mediaLikeKey = RedisKeyUtils.getMediaLikeSetKey(mediaType, mediaId);
        return redisTemplate.opsForSet().isMember(mediaLikeKey, uuid) ? true : false;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void star(InteractionParam param) {
        UserStar userStar = new UserStar();
        BeanUtils.copyProperties(param, userStar);
        // todo 暂时只提供单个收藏夹
        userStar.setPackId(0L);
        int mediaStarStatus = userStarService.findMediaStarStatus(param.getUuid(), param.getMediaType(), param.getMediaId());
        VideoProfile videoProfile = videoProfileService.queryByUvid(param.getMediaId());
        if (videoProfile == null) {
            log.error("uvid: {}, 数据异常，没有profile数据", param.getMediaId());
        }
        if (mediaStarStatus == 1) {
            // 取消收藏
            userStarService.delStar(userStar);
            if (videoProfile != null) {
                videoProfile.setStarCount(videoProfile.getStarCount() - 1);
                videoProfileService.updateVideoProfile(videoProfile);
            }
        } else {
            // 添加收藏
            userStarService.addStar(userStar);
            if (videoProfile != null) {
                videoProfile.setStarCount(videoProfile.getStarCount() + 1);
                videoProfileService.updateVideoProfile(videoProfile);
            }
        }
        // 计算视频综合评分
        if (MediaTypeEnum.VIDEO.getValue().equals(param.getMediaType())) {
            String mediaScoreKey = RedisKeyUtils.getMediaScoreKey(MediaTypeEnum.VIDEO.getValue());
            redisTemplate.opsForSet().add(mediaScoreKey, param.getMediaId());
        }
    }

    @Override
    public int findMediaLikedCount(Integer mediaType, Long mediaId) {
        String mediaKey = RedisKeyUtils.getMediaKey(mediaType, mediaId);
        Integer count = (Integer) redisTemplate.opsForHash().get(mediaKey, RedisKeyUtils.likedCountHashKey);
        return count == null ? 0 : count;
    }
    
    @Override
    public int findMediaViewCount(Integer mediaType, Long mediaId) {
        String mediaKey = RedisKeyUtils.getMediaKey(mediaType, mediaId);
        Integer count = (Integer) redisTemplate.opsForHash().get(mediaKey, RedisKeyUtils.viewCountHashKey);
        return count == null ? 0 : count;
    }
    
    @Override
    public void follow(Long uuid) {
        Long loginUuid = OperatorHelper.getLoginUuid();
        redisTemplate.execute(new SessionCallback() {
            @Override
            public Object execute(RedisOperations operations) throws DataAccessException {
                // 被关注方
                String fansKey = RedisKeyUtils.getUserFansKey(uuid);
                // 登录用户，即主动方
                String followKey = RedisKeyUtils.getUserFollowKey(loginUuid);
                operations.multi();
                // 被关注用户的粉丝列表加
                operations.opsForZSet().add(fansKey, loginUuid, System.currentTimeMillis());
                // 关注用户的关注列表加
                operations.opsForZSet().add(followKey, uuid, System.currentTimeMillis());

                return operations.exec();
            }
        });
    }

    @Override
    public void unfollow(Long uuid) {
        Long loginUuid = OperatorHelper.getLoginUuid();
        redisTemplate.execute(new SessionCallback() {
            @Override
            public Object execute(RedisOperations operations) throws DataAccessException {
                // 被关注方
                String fansKey = RedisKeyUtils.getUserFansKey(uuid);
                // 登录用户，即主动方
                String followKey = RedisKeyUtils.getUserFollowKey(loginUuid);
                operations.multi();
                // 被取关用户的粉丝-1
                operations.opsForZSet().remove(fansKey, loginUuid);
                // 取关用户的关注-1
                operations.opsForZSet().remove(followKey, uuid);

                return operations.exec();
            }
        });
    }

    @Override
    public boolean hasFollowed(Long loginUuid, Long uuid) {
        if (loginUuid == null) {
            // 如果未登录，提示未关注
            return  false;
        }
        // 被关注方
        String fansKey = RedisKeyUtils.getUserFansKey(uuid);
        // 登录用户，即主动方
        String followKey = RedisKeyUtils.getUserFollowKey(loginUuid);
        Double fans = redisTemplate.opsForZSet().score(fansKey, loginUuid);
        Double follow = redisTemplate.opsForZSet().score(followKey, uuid);
        // 满足关注方关注列表中存在被关注方，且被关注方粉丝列表中存在关注方
        if (fans != null && follow != null) {
            return true;
        } else {
            return false;
        }
    }
    
    @Override
    public Set<Long> findUserFollowList(Long uuid, Integer pageNum, Integer pageSize) {
        String userFollowKey = RedisKeyUtils.getUserFollowKey(uuid);
        int offset = (pageNum - 1) * pageSize;
        return (Set<Long>) redisTemplate.opsForZSet().reverseRange(userFollowKey, offset, offset + pageSize - 1);
    }

    

    @Override
    public long findUserFansCount(Long uuid) {
        String userFansKey = RedisKeyUtils.getUserFansKey(uuid);
        return redisTemplate.opsForZSet().zCard(userFansKey);
    }
    
    @Override
    public long findUserFollowCount(Long uuid) {
        String userFollowKey = RedisKeyUtils.getUserFollowKey(uuid);
        return redisTemplate.opsForZSet().zCard(userFollowKey);
    }

    @Override
    public int findMediaClickCount(Integer mediaType, Long mediaId) {
        String mediaKey = RedisKeyUtils.getMediaKey(mediaType, mediaId);
        Integer count = (Integer) redisTemplate.opsForHash().get(mediaKey, RedisKeyUtils.clickCountHashKey);
        return count == null ? 0 : count;
    }

    @Override
    public int findUserLikedCount(Long uuid) {
        String userKey = RedisKeyUtils.getUserKey(uuid);
        Integer count = (Integer) redisTemplate.opsForHash().get(userKey, RedisKeyUtils.likedCountHashKey);
        return count == null ? 0 : count;
    }

    @Override
    public int findUserViewCount(Long uuid) {
        String userKey = RedisKeyUtils.getUserKey(uuid);
        Integer count = (Integer) redisTemplate.opsForHash().get(userKey, RedisKeyUtils.viewCountHashKey);
        return count == null ? 0 : count;
    }

    @Override
    public void exposure(InteractionParam param) {
        // 1、媒体统计量++
        String mediaKey = RedisKeyUtils.getMediaKey(param.getMediaType(), param.getMediaId());
        redisTemplate.opsForHash().increment(mediaKey, RedisKeyUtils.exposureCountHashKey, 1);
        // 计算视频评分
        if (MediaTypeEnum.VIDEO.getValue().equals(param.getMediaType())) {
            String mediaScoreKey = RedisKeyUtils.getMediaScoreKey(MediaTypeEnum.VIDEO.getValue());
            redisTemplate.opsForSet().add(mediaScoreKey, param.getMediaId());
        }
    }

    @Override
    public int findMediaExposureCount(Integer mediaType, Long mediaId) {
        String mediaKey = RedisKeyUtils.getMediaKey(mediaType, mediaId);
        Integer count = (Integer) redisTemplate.opsForHash().get(mediaKey, RedisKeyUtils.exposureCountHashKey);
        return count == null ? 0 : count;
    }
}
